1 module backtraced;
2 version(Windows) version = UseBacktraced;
3 else version(Android) version = UseNullBacktraced;
4 else version(linux) version = UseBacktraced;
5 else version = UseNullBacktraced;
6 
7 
8 
9 version(UseNullBacktraced)
10 void backtraced_Register() { }
11 
12 version(UseBacktraced):
13 import core.demangle;
14 
15 version (Windows)
16 {
17 
18     pragma(lib, "dbghelp.lib");
19     import core.sys.windows.windef;
20     import core.sys.windows.imagehlp;
21     import core.sys.windows.winbase;
22     import core.sys.windows.dbghelp;
23     import core.stdc.stdlib : free, calloc;
24     import core.stdc.stdio : fprintf, stderr;
25     import core.stdc.string : memcpy, strncmp, strlen;
26 
27     struct SYMBOL_INFO
28     {
29         ULONG SizeOfStruct;
30         ULONG TypeIndex;
31         ULONG64[2] Reserved;
32         ULONG Index;
33         ULONG Size;
34         ULONG64 ModBase;
35         ULONG Flags;
36         ULONG64 Value;
37         ULONG64 Address;
38         ULONG Register;
39         ULONG Scope;
40         ULONG Tag;
41         ULONG NameLen;
42         ULONG MaxNameLen;
43         CHAR[1] Name;
44     }
45 
46     extern (Windows) USHORT RtlCaptureStackBackTrace(ULONG FramesToSkip, ULONG FramesToCapture, PVOID* BackTrace, PULONG BackTraceHash);
47     extern (Windows) BOOL SymFromAddr(HANDLE hProcess, DWORD64 Address, PDWORD64 Displacement, SYMBOL_INFO* Symbol);
48     extern (Windows) BOOL SymGetLineFromAddr64(HANDLE hProcess, DWORD64 dwAddr, PDWORD pdwDisplacement, IMAGEHLP_LINEA64* line);
49 
50     version(DigitalMars)
51     void printStackTrace()
52     {
53         import hip.util.string;
54         enum MAX_DEPTH = 256;
55         void*[MAX_DEPTH] stack;
56 
57         HANDLE process = GetCurrentProcess();
58         ushort frames = RtlCaptureStackBackTrace(0, MAX_DEPTH, stack.ptr, null);
59         SYMBOL_INFO* symbol = cast(SYMBOL_INFO*) calloc((SYMBOL_INFO.sizeof) + 256, 1);
60         symbol.MaxNameLen = 255;
61         symbol.SizeOfStruct = SYMBOL_INFO.sizeof;
62 
63         IMAGEHLP_LINEA64 line = void;
64         line.SizeOfStruct = SYMBOL_INFO.sizeof;
65 
66         DWORD dwDisplacement;
67 
68         static int ends_with(const(char)* str, const(char)* suffix)
69         {
70             if (!str || !suffix)
71                 return 0;
72             size_t lenstr = strlen(str);
73             size_t lensuffix = strlen(suffix);
74             if (lensuffix > lenstr)
75                 return 0;
76             return strncmp(str + lenstr - lensuffix, suffix, lensuffix) == 0;
77         }
78 
79         for (uint i = 0; i < frames; i++)
80         {
81             SymFromAddr(process, cast(DWORD64)(stack[i]), null, symbol);
82             SymGetLineFromAddr64(process, cast(DWORD64)(stack[i]), &dwDisplacement, &line);
83 
84             // auto f = frames - i - 1;
85             char[] funcName = demangle(symbol.Name.ptr[0..symbol.NameLen]);
86             auto fname = line.FileName;
87             auto lnum = line.LineNumber;
88 
89                 if (ends_with(fname, __FILE__))
90                     continue; // skip trace from this module
91                 if(funcName.indexOf("_d_run_main2") != -1)
92                     break;
93 
94             fprintf(stderr, "%s:%i - %.*s\n", fname, lnum, cast(int)funcName.length, funcName.ptr);
95         }
96 
97         free(symbol);
98     }
99     extern (Windows) LONG TopLevelExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo)
100     {
101         import hip.util.system;
102         enum NullPointerMessage = 0xC0000005;
103         import hip.util.conv;
104 
105         string msg;
106         switch(pExceptionInfo.ExceptionRecord.ExceptionCode)
107         {
108             case NullPointerMessage:
109                 msg = "Caught NullPointerException (0xC0000005)";
110                 break;
111             default:
112                 msg = "Caught Exception (0x"~toHex(pExceptionInfo.ExceptionRecord.ExceptionCode)~")";
113                 break;
114         }
115 
116         version(DigitalMars) ///DMD can't print the stack trace.
117         {
118             fprintf(stderr, "%.*s\n", cast(int)msg.length, msg.ptr);
119             printStackTrace();
120         }
121         else
122             throw new Exception(msg);
123         return EXCEPTION_CONTINUE_SEARCH;
124     }
125 
126     void backtraced_Register()
127     {
128         SymInitialize(GetCurrentProcess(), null, true);
129         SymSetOptions(SYMOPT_LOAD_LINES | SYMOPT_DEBUG);
130         SetUnhandledExceptionFilter(&TopLevelExceptionHandler);
131     }
132 }
133 
134 version (linux)
135 {
136     import core.stdc.signal : SIGSEGV, SIGFPE, SIGILL, SIGABRT, signal;
137     import core.stdc.stdlib : free, exit;
138     import core.stdc.string : strlen, memcpy;
139     import core.stdc.stdio : fprintf, stderr, sprintf, fgets, fclose, FILE;
140     import core.sys.posix.unistd : STDERR_FILENO, readlink;
141     import core.sys.posix.signal : SIGUSR1;
142     import core.sys.posix.stdio : popen, pclose;
143     import core.sys.linux.execinfo : backtrace, backtrace_symbols;
144     import core.sys.linux.dlfcn : dladdr, dladdr1, Dl_info, RTLD_DL_LINKMAP;
145     import core.sys.linux.link : link_map;
146     import core.demangle : demangle;
147 
148     void backtraced_Register()
149     {
150         signal(SIGSEGV, &handler);
151         signal(SIGUSR1, &handler);
152     }
153 
154     // TODO: clean this mess
155     // TODO: use core.demangle instead
156     extern (C) void handler(int sig) nothrow @nogc
157     {
158         enum MAX_DEPTH = 32;
159 
160         string signal_string;
161         switch (sig)
162         {
163         case SIGSEGV:
164             signal_string = "SIGSEGV";
165             break;
166         case SIGFPE:
167             signal_string = "SIGFPE";
168             break;
169         case SIGILL:
170             signal_string = "SIGILL";
171             break;
172         case SIGABRT:
173             signal_string = "SIGABRT";
174             break;
175         default:
176             signal_string = "unknown";
177             break;
178         }
179 
180         fprintf(stderr, "-------------------------------------------------------------------+\r\n");
181         fprintf(stderr, "Received signal '%s' (%d)\r\n", signal_string.ptr, sig);
182         fprintf(stderr, "-------------------------------------------------------------------+\r\n");
183 
184         void*[MAX_DEPTH] trace;
185         int stack_depth = backtrace(&trace[0], MAX_DEPTH);
186         char** strings = backtrace_symbols(&trace[0], stack_depth);
187 
188         enum BUF_SIZE = 1024;
189         char[BUF_SIZE] syscom = 0;
190         char[BUF_SIZE] my_exe = 0;
191         char[BUF_SIZE] output = 0;
192 
193         readlink("/proc/self/exe", &my_exe[0], BUF_SIZE);
194 
195         fprintf(stderr, "executable: %s\n", &my_exe[0]);
196         fprintf(stderr, "backtrace: %i\n", stack_depth);
197 
198         for (auto i = 2; i < stack_depth; ++i)
199         {
200             auto line = strings[i];
201             auto len = strlen(line);
202             bool insideParenthesis;
203             int startParenthesis;
204             int endParenthesis;
205             for (int j = 0; j < len; j++)
206             {
207                 // ()
208                 if (!insideParenthesis && line[j] == '(')
209                 {
210                     insideParenthesis = true;
211                     startParenthesis = j + 1;
212                 }
213                 else if (insideParenthesis && line[j] == ')')
214                 {
215                     insideParenthesis = false;
216                     endParenthesis = j;
217                 }
218             }
219             auto addr = convert_to_vma(cast(size_t) trace[i]);
220             FILE* fp;
221 
222             auto locLen = sprintf(&syscom[0], "addr2line -e %s %p | ddemangle", &my_exe[0], addr);
223             fp = popen(&syscom[0], "r");
224 
225             auto loc = fgets(&output[0], output.length, fp);
226             fclose(fp);
227 
228             // printf("loc: %s\n", loc);
229 
230             auto getLen = strlen(output.ptr);
231 
232             char[256] func = 0;
233             memcpy(func.ptr, &line[startParenthesis], (endParenthesis - startParenthesis));
234             sprintf(&syscom[0], "echo '%s' | ddemangle", func.ptr);
235             fp = popen(&syscom[0], "r");
236 
237             output[getLen - 1] = ' '; // strip new line
238             auto locD = fgets(&output[getLen], cast(int)(output.length - getLen), fp);
239             fclose(fp);
240 
241             fprintf(stderr, "%s", output.ptr);
242         }
243         exit(-1);
244     }
245 
246     // https://stackoverflow.com/questions/56046062/linux-addr2line-command-returns-0/63856113#63856113
247     size_t convert_to_vma(size_t addr) nothrow @nogc
248     {
249         Dl_info info;
250         link_map* link_map;
251         dladdr1(cast(void*) addr, &info, cast(void**)&link_map, RTLD_DL_LINKMAP);
252         return addr - link_map.l_addr;
253     }
254 }